package com.demon.framework.boot.failover;

import com.demon.framework.boot.context.SpringContext;
import com.demon.framework.boot.job.AbstractTask;
import com.demon.framework.boot.job.JobForTaskService;
import com.demon.framework.common.model.constants.LogConstants;
import com.demon.framework.util.request.RequestContextUtil;
import com.demon.framework.util.serialize.ProtostuffUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.slf4j.MDC;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Field;
import java.net.URI;
import java.util.Date;

/**
 * 抽象的 业务重试 Job
 *
 * @author Demon-HY
 * @date 2020/7/24 16:44
 */
@Slf4j
public abstract class AbstractTaskFailoverJob implements Job {

    @Override
    public final void execute(JobExecutionContext context) {
        JobDataMap map = context.getJobDetail().getJobDataMap();
        String id = map.getString("id");
        MDC.put(LogConstants.UNIQUE_ID, "F_" + RequestContextUtil.genRequestId());

        log.info("执行业务重试任务,taskId:{}", id);

        //查询出failoverBean
        TaskFailoverBean failoverBean = null;

        try {
            failoverBean = getJdbcTemplate().queryForObject(TaskFailoverBean.getSqlForFindById(Long.valueOf(id)), TaskFailoverBean.BEAN_BEAN_PROPERTY_ROW_MAPPER);

            //重放http请求
            RequestEntity requestEntity = ProtostuffUtil.deserializeFromBase64(failoverBean.getSerializeData(), RequestEntity.class);

            Field fieldUrl = ReflectionUtils.findField(RequestEntity.class, "url");
            fieldUrl.setAccessible(true);
            ReflectionUtils.setField(fieldUrl, requestEntity, new URI(requestEntity.getUrl().toString()));

            ResponseEntity<String> exchange = getRestTemplate().exchange(requestEntity, String.class);
            if (isSuccess(exchange)) {
                //重试成功后 删除重试task_failover
                log.info("执行重试任务成功,数据详情:{}", failoverBean);
                getJdbcTemplate().execute(TaskFailoverBean.getSqlForClear(failoverBean.getId()));
            } else {
                log.warn("执行重试失败,数据详情:{},请求返回：{}", failoverBean, exchange.getBody());
                retry(failoverBean, exchange.getBody());
            }
        } catch (Throwable e) {
            if (null != failoverBean) {
                log.error("执行重试异常,数据详情:{}", failoverBean, e);
                retry(failoverBean, e.getMessage());
            } else {
                log.error("查询重试任务失败,taskId:{}", id, e);
            }
        } finally {
            MDC.remove(LogConstants.UNIQUE_ID);
        }

    }


    /**
     * 下一次重试
     *
     * @param failoverBean 本次重试数据详情
     * @param reason       本次失败的原因
     */
    private void retry(TaskFailoverBean failoverBean, String reason) {
        //再一次重试
        int nextIntervalTime = getNextIntervalTime(failoverBean.getRetryCount() + 1);
        Date nextTime = DateUtils.addSeconds(new Date(), nextIntervalTime);
        failoverBean.setNextTime(nextTime);
        //重试的原因用 | 隔开
        String reasons = failoverBean.getReason() + " | " + reason;
        failoverBean.setReason(reasons);
        //重试更新task_failover
        getJdbcTemplate().execute(failoverBean.getSqlForRetry());

        if (nextIntervalTime > 0) {
            //重置重试任务到 nextTime
            JobForTaskService jobForTaskService = SpringContext.getBean(JobForTaskService.class);

            jobForTaskService.resetJobTime(new AbstractTask(failoverBean.getId().toString()) {
                @Override
                protected Class<? extends AbstractTaskFailoverJob> getJobClass() {
                    return AbstractTaskFailoverJob.this.getClass();
                }

                @Override
                protected String getJobGroup() {
                    return TaskFailoverBean.JOB_GROUP_FOR_FAILOVER;
                }
            }, nextTime);

        } else {
            //TODO 预警
            log.warn("failover任务重试结束,任务详情:{}", failoverBean);
        }
    }

    /**
     * 是否重试成功
     *
     * @param stringResponseEntity 重试返回的消息体
     */
    protected abstract boolean isSuccess(ResponseEntity<String> stringResponseEntity);

    /**
     * 获取下一次重试的间隔
     *
     * @param nowTimes 当前重试的次数,从0开始
     * @return 重试结束请求返回 0
     */
    public abstract int getNextIntervalTime(int nowTimes);


    /**
     * 获取任务类型，默认为 类名，子类可重写 @Override
     */
    public String getTaskType() {
        return this.getClass().getSimpleName();
    }

    /**
     * 获取RestTemplate ，子类可重写 @Override
     */
    protected RestTemplate getRestTemplate() {
        return SpringContext.getBean(ClassUtils.getShortNameAsProperty(RestTemplate.class), RestTemplate.class);
    }

    /**
     * 获取数据库连接
     */
    private JdbcTemplate getJdbcTemplate() {
        return SpringContext.getBean(TaskFailoverService.class).getJdbcTemplate();
    }

}
